#include <float.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <atomic>
#include <mutex>
#include <string>
#include <thread>
#include "../public/VHACD.h"

#define ENABLE_ASYNC 1

#define HACD_ALLOC(x) malloc(x)
#define HACD_FREE(x) free(x)
#define HACD_ASSERT(x) assert(x)

namespace VHACD {

class MyHACD_API : public VHACD::IVHACD,
                   public VHACD::IVHACD::IUserCallback,
                   VHACD::IVHACD::IUserLogger {
 public:
  MyHACD_API(void) { mVHACD = VHACD::CreateVHACD(); }

  virtual ~MyHACD_API(void) {
    releaseHACD();
    Cancel();
    mVHACD->Release();
  }

  virtual bool Compute(const double* const _points,
                       const uint32_t countPoints,
                       const uint32_t* const _triangles,
                       const uint32_t countTriangles,
                       const Parameters& _desc) final {
#if ENABLE_ASYNC
    Cancel();  // if we previously had a solution running; cancel it.
    releaseHACD();
    // We need to copy the input vertices and triangles into our own buffers so
    // we can operate on them safely from the background thread.
    mVertices = (double*)HACD_ALLOC(sizeof(double) * countPoints * 3);
    mIndices = (uint32_t*)HACD_ALLOC(sizeof(uint32_t) * countTriangles * 3);
    memcpy(mVertices, _points, sizeof(double) * countPoints * 3);
    memcpy(mIndices, _triangles, sizeof(uint32_t) * countTriangles * 3);
    mRunning = true;
    mThread = new std::thread([this, countPoints, countTriangles, _desc]() {
      ComputeNow(mVertices, countPoints, mIndices, countTriangles, _desc);
      mRunning = false;
    });
#else
    releaseHACD();
    ComputeNow(_points, countPoints, _triangles, countTriangles, _desc);
#endif
    return true;
  }
  virtual bool computeVoxelField(const double* const points,
                                 const uint32_t countPoints,
                                 const uint32_t* const triangles,
                                 const uint32_t countTriangles,
                                 const uint32_t resolution) {
    return mVHACD->computeVoxelField(points, countPoints, triangles,
                                     countTriangles, resolution);
  }

  virtual Volume* getVoxelField() { return mVHACD->getVoxelField(); }

  bool ComputeNow(const double* const points,
                  const uint32_t countPoints,
                  const uint32_t* const triangles,
                  const uint32_t countTriangles,
                  const Parameters& _desc) {
    uint32_t ret = 0;

    mHullCount = 0;
    mCallback = _desc.m_callback;
    mLogger = _desc.m_logger;

    IVHACD::Parameters desc = _desc;
    // Set our intercepting callback interfaces if non-null
    desc.m_callback = desc.m_callback ? this : nullptr;
    desc.m_logger = desc.m_logger ? this : nullptr;

    if (countPoints) {
      bool ok =
          mVHACD->Compute(points, countPoints, triangles, countTriangles, desc);
      if (ok) {
        ret = mVHACD->GetNConvexHulls();
        mHulls = new IVHACD::ConvexHull[ret];
        for (uint32_t i = 0; i < ret; i++) {
          VHACD::IVHACD::ConvexHull vhull;
          mVHACD->GetConvexHull(i, vhull);
          VHACD::IVHACD::ConvexHull h;
          h.m_nPoints = vhull.m_nPoints;
          h.m_points = (double*)HACD_ALLOC(sizeof(double) * 3 * h.m_nPoints);
          memcpy(h.m_points, vhull.m_points, sizeof(double) * 3 * h.m_nPoints);
          h.m_nTriangles = vhull.m_nTriangles;
          h.m_triangles =
              (uint32_t*)HACD_ALLOC(sizeof(uint32_t) * 3 * h.m_nTriangles);
          memcpy(h.m_triangles, vhull.m_triangles,
                 sizeof(uint32_t) * 3 * h.m_nTriangles);
          h.m_volume = vhull.m_volume;
          h.m_center[0] = vhull.m_center[0];
          h.m_center[1] = vhull.m_center[1];
          h.m_center[2] = vhull.m_center[2];
          mHulls[i] = h;
          if (mCancel) {
            ret = 0;
            break;
          }
        }
      }
    }

    mHullCount = ret;
    return ret ? true : false;
  }

  void releaseHull(VHACD::IVHACD::ConvexHull& h) {
    HACD_FREE((void*)h.m_triangles);
    HACD_FREE((void*)h.m_points);
    h.m_triangles = nullptr;
    h.m_points = nullptr;
  }

  virtual void GetConvexHull(const uint32_t index,
                             VHACD::IVHACD::ConvexHull& ch) const final {
    if (index < mHullCount) {
      ch = mHulls[index];
    }
  }

  void releaseHACD(
      void)  // release memory associated with the last HACD request
  {
    for (uint32_t i = 0; i < mHullCount; i++) {
      releaseHull(mHulls[i]);
    }
    delete[] mHulls;
    mHulls = nullptr;
    mHullCount = 0;
    HACD_FREE(mVertices);
    mVertices = nullptr;
    HACD_FREE(mIndices);
    mIndices = nullptr;
  }

  virtual void release(void)  // release the HACD_API interface
  {
    delete this;
  }

  virtual uint32_t getHullCount(void) { return mHullCount; }

  virtual void Cancel() final {
    if (mRunning) {
      mVHACD->Cancel();  // Set the cancel signal to the base VHACD
    }
    if (mThread) {
      mThread->join();  // Wait for the thread to fully exit before we delete
                        // the instance
      delete mThread;
      mThread = nullptr;
      Log("Convex Decomposition thread canceled\n");
    }
    mCancel = false;  // clear the cancel semaphore
  }

  virtual bool Compute(const float* const points,
                       const uint32_t countPoints,
                       const uint32_t* const triangles,
                       const uint32_t countTriangles,
                       const Parameters& params) final {
    double* vertices = (double*)HACD_ALLOC(sizeof(double) * countPoints * 3);
    const float* source = points;
    double* dest = vertices;
    for (uint32_t i = 0; i < countPoints; i++) {
      dest[0] = source[0];
      dest[1] = source[1];
      dest[2] = source[2];
      dest += 3;
      source += 3;
    }

    bool ret =
        Compute(vertices, countPoints, triangles, countTriangles, params);
    HACD_FREE(vertices);
    return ret;
  }

  virtual bool computeVoxelField(const float* const points,
                                 const uint32_t countPoints,
                                 const uint32_t* const triangles,
                                 const uint32_t countTriangles,
                                 const uint32_t resolution) final {
    double* vertices = (double*)HACD_ALLOC(sizeof(double) * countPoints * 3);
    const float* source = points;
    double* dest = vertices;
    for (uint32_t i = 0; i < countPoints; i++) {
      dest[0] = source[0];
      dest[1] = source[1];
      dest[2] = source[2];
      dest += 3;
      source += 3;
    }

    bool ret = computeVoxelField(vertices, countPoints, triangles,
                                 countTriangles, resolution);
    HACD_FREE(vertices);
    return ret;
  }

  virtual uint32_t GetNConvexHulls() const final {
    processPendingMessages();
    return mHullCount;
  }

  virtual void Clean(void) final  // release internally allocated memory
  {
    Cancel();
    releaseHACD();
    mVHACD->Clean();
  }

  virtual void Release(void) final  // release IVHACD
  {
    delete this;
  }

  virtual bool OCLInit(void* const oclDevice,
                       IVHACD::IUserLogger* const logger = 0) final {
    return mVHACD->OCLInit(oclDevice, logger);
  }

  virtual bool OCLRelease(IVHACD::IUserLogger* const logger = 0) final {
    return mVHACD->OCLRelease(logger);
  }

  virtual void Update(const double overallProgress,
                      const double stageProgress,
                      const double operationProgress,
                      const char* const stage,
                      const char* const operation) final {
    mMessageMutex.lock();
    mHaveUpdateMessage = true;
    mOverallProgress = overallProgress;
    mStageProgress = stageProgress;
    mOperationProgress = operationProgress;
    mStage = std::string(stage);
    mOperation = std::string(operation);
    mMessageMutex.unlock();
  }

  virtual void Log(const char* const msg) final {
    mMessageMutex.lock();
    mHaveLogMessage = true;
    mMessage = std::string(msg);
    mMessageMutex.unlock();
  }

  virtual bool IsReady(void) const final {
    processPendingMessages();
    return !mRunning;
  }

  // As a convenience for the calling application we only send it update and log
  // messages from it's own main thread.  This reduces the complexity burden on
  // the caller by making sure it only has to deal with log messages in it's
  // main application thread.
  void processPendingMessages(void) const {
    // If we have a new update message and the user has specified a callback we
    // send the message and clear the semaphore
    if (mHaveUpdateMessage && mCallback) {
      mMessageMutex.lock();
      mCallback->Update(mOverallProgress, mStageProgress, mOperationProgress,
                        mStage.c_str(), mOperation.c_str());
      mHaveUpdateMessage = false;
      mMessageMutex.unlock();
    }
    // If we have a new log message and the user has specified a callback we
    // send the message and clear the semaphore
    if (mHaveLogMessage && mLogger) {
      mMessageMutex.lock();
      mLogger->Log(mMessage.c_str());
      mHaveLogMessage = false;
      mMessageMutex.unlock();
    }
  }

  // Will compute the center of mass of the convex hull decomposition results
  // and return it in 'centerOfMass'.  Returns false if the center of mass could
  // not be computed.
  virtual bool ComputeCenterOfMass(double centerOfMass[3]) const {
    bool ret = false;

    centerOfMass[0] = 0;
    centerOfMass[1] = 0;
    centerOfMass[2] = 0;

    if (mVHACD && IsReady()) {
      ret = mVHACD->ComputeCenterOfMass(centerOfMass);
    }
    return ret;
  }

 private:
  double* mVertices{nullptr};
  uint32_t* mIndices{nullptr};
  std::atomic<uint32_t> mHullCount{0};
  VHACD::IVHACD::ConvexHull* mHulls{nullptr};
  VHACD::IVHACD::IUserCallback* mCallback{nullptr};
  VHACD::IVHACD::IUserLogger* mLogger{nullptr};
  VHACD::IVHACD* mVHACD{nullptr};
  std::thread* mThread{nullptr};
  std::atomic<bool> mRunning{false};
  std::atomic<bool> mCancel{false};

  // Thread safe caching mechanism for messages and update status.
  // This is so that caller always gets messages in his own thread
  // Member variables are marked as 'mutable' since the message dispatch
  // function is called from const query methods.
  mutable std::mutex mMessageMutex;
  mutable std::atomic<bool> mHaveUpdateMessage{false};
  mutable std::atomic<bool> mHaveLogMessage{false};
  mutable double mOverallProgress{0};
  mutable double mStageProgress{0};
  mutable double mOperationProgress{0};
  mutable std::string mStage;
  mutable std::string mOperation;
  mutable std::string mMessage;
};

IVHACD* CreateVHACD_ASYNC(void) {
  MyHACD_API* m = new MyHACD_API;
  return static_cast<IVHACD*>(m);
}

};  // namespace VHACD
